package ledger

import (
	"encoding/hex"
	"errors"
	"fmt"
	"strconv"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/crypto"
	"hundsun.com/hsl/hschain/protos"
	"hundsun.com/hsl/hschain/protos/common"
)

// GetXXXNamespace ...
func (m *NsKV) GetXXXNamespace() string {
	return protos.EsIndexStateDB
}

// GetXXXKey ...
func (m *NsKV) GetXXXKey() string {
	if m.Key == "" || m.Namespace == "" {
		return ""
	}
	return fmt.Sprintf("%s%s%s", m.Namespace, protos.EsDocIDSeparator, m.Key)
}

// GetXXXSearchFields ...
func (m *NsKV) GetXXXSearchFields() map[string]interface{} {
	return nil
}

// Equals ...
func (m *Transaction) Equals(other *Transaction) bool {
	if other == nil {
		return false
	}
	mBytes := m.marshal()
	otherBytes := other.marshal()
	return byteSliceEqual(mBytes, otherBytes)
}

func byteSliceEqual(a, b []byte) bool {
	if len(a) != len(b) {
		return false
	}

	if (a == nil) != (b == nil) {
		return false
	}

	for i, v := range a {
		if v != b[i] {
			return false
		}
	}

	return true
}

func (m *Transaction) clone() *Transaction {
	clonedTx := &Transaction{}
	clonedTx.Ty = m.Ty
	clonedTx.From = m.From
	clonedTx.To = m.To
	clonedTx.ContractId = m.ContractId
	clonedTx.Value = m.Value
	clonedTx.Fee = m.Fee
	clonedTx.Payload = m.Payload
	clonedTx.Nonce = m.Nonce
	clonedTx.CreateTime = m.CreateTime
	clonedTx.TTL = m.TTL
	clonedTx.Signature = m.Signature
	clonedTx.ChainID = m.ChainID
	return clonedTx
}

func (m *Transaction) marshal() []byte {
	bytes, err := proto.Marshal(m)
	if err != nil {
		panic(err)
	}
	return bytes
}

// Hash ...
func (m *Transaction) Hash() protos.Hash {
	cloneTx := m.clone()
	cloneTx.Signature = nil
	hashBytes, err := crypto.Hash(cloneTx.marshal())
	if err != nil {
		panic(err)
	}
	return hashBytes
}

// GetXXXNamespace ...
func (m *Transaction) GetXXXNamespace() string {
	return protos.EsIndexTx
}

// GetXXXKey ...
func (m *Transaction) GetXXXKey() string {
	return hex.EncodeToString(m.Hash())
}

// GetXXXSearchFields ...
func (m *Transaction) GetXXXSearchFields() map[string]interface{} {
	searchFields := make(map[string]interface{})
	searchFields["from"] = m.From
	searchFields["to"] = m.To
	searchFields["contractId"] = m.ContractId
	return searchFields
}

// Sign SignableMessage接口
func (m *Transaction) Sign(keyPair crypto.KeyPair) {
	m.Signature = nil
	signatureBytes, err := keyPair.Sign(m.marshal())
	if err != nil {
		panic(err)
	}
	m.Signature = &common.Signature{
		Algorithm: uint32(keyPair.GetSignatureAlgorithm()),
		PublicKey: keyPair.PublicKey().Bytes(),
		Signature: signatureBytes,
	}
}

// Verify tx verify
func (m *Transaction) Verify() bool {
	if m.Signature == nil || len(m.Signature.Signature) == 0 || len(m.Signature.PublicKey) == 0 {
		return false
	}
	clonedTx := m.clone()
	clonedTx.Signature = nil
	data := clonedTx.marshal()
	publicKey, err := crypto.ParserPublicKeyWithSignatureAlgorithm(m.Signature.PublicKey, int(m.Signature.Algorithm))
	if err != nil {
		return false
	}
	ok, err := publicKey.Verify(data, m.Signature.Signature)
	if err != nil {
		return false
	}
	return ok

}

// GetContractAddressByID get contractAddress by ID
func (m *Transaction) GetContractAddressByID() string {
	if m != nil {
		var tmpBytes []byte
		tmpBytes = append(tmpBytes, []byte(m.ContractId.Name)...)
		tmpBytes = append(tmpBytes, []byte(m.ContractId.Version)...)
		hashBytes, err := crypto.Hash(tmpBytes)
		if err != nil {
			return ""
		}
		return hex.EncodeToString(hashBytes[12:])
	}
	return ""
}

func (m *Block) clone() *Block {
	clonedBlk := &Block{
		Header:       nil,
		Transactions: nil,
	}
	clonedBlk.Header = &BlockHeader{
		ParentHash:            m.Header.ParentHash,
		Number:                m.Header.Number,
		Signature:             m.Header.Signature,
		Difficulty:            m.Header.Difficulty,
		BlockTime:             m.Header.BlockTime,
		CheckBlock:            m.Header.CheckBlock,
		StateHashOfCheckBlock: m.Header.StateHashOfCheckBlock,
		BlockSequence:         m.Header.BlockSequence,
		Consensus:             m.Header.Consensus,
	}
	clonedBlk.Transactions = m.Transactions
	return clonedBlk
}

// Marshal ...
func (m *Block) Marshal() []byte {
	bytes, err := proto.Marshal(m)
	if err != nil {
		panic(err)
	}
	return bytes
}

// Hash block to hash
func (m *Block) Hash() protos.Hash {
	clonedBlk := m.clone()
	clonedBlk.Header.Consensus = nil
	clonedBlk.Header.BlockSequence = 0
	clonedBlk.Header.BlockSequenceSignature = nil
	clonedBlk.Header.Signature = nil
	hash, err := crypto.Hash(clonedBlk.Marshal())
	if err != nil {
		panic(err)
	}
	return hash
}

// GetXXXNamespace ...
func (m *Block) GetXXXNamespace() string {
	return protos.EsIndexBlock
}

// GetXXXKey ...
func (m *Block) GetXXXKey() string {
	if m.Header != nil {
		return strconv.FormatUint(m.Header.Number, 10)
	}
	return ""
}

// GetXXXSearchFields ...
func (m *Block) GetXXXSearchFields() map[string]interface{} {
	if m.Header == nil {
		return nil
	}
	searchFields := make(map[string]interface{})
	searchFields["number"] = m.Header.Number
	searchFields["blockSequence"] = m.Header.BlockSequence
	return searchFields
}

// Sign 实现SignableMessage接口
func (m *Block) Sign(keyPair crypto.KeyPair) {
	clonedBlk := m.clone()
	clonedBlk.Header.Consensus = nil
	clonedBlk.Header.BlockSequence = 0
	clonedBlk.Header.BlockSequenceSignature = nil
	clonedBlk.Header.Signature = nil
	signatureBytes, err := keyPair.Sign(clonedBlk.Marshal())
	if err != nil {
		panic(err)
	}
	m.Header.Signature = &common.Signature{
		Algorithm: uint32(keyPair.GetSignatureAlgorithm()),
		PublicKey: keyPair.PublicKey().Bytes(),
		Signature: signatureBytes,
	}
}

// Verify ...
func (m *Block) Verify() bool {
	sig := m.Header.Signature
	if sig == nil || len(sig.Signature) == 0 || len(sig.PublicKey) == 0 {
		return false
	}
	clonedBlk := m.clone()
	clonedBlk.Header.Consensus = nil
	clonedBlk.Header.BlockSequence = 0
	clonedBlk.Header.BlockSequenceSignature = nil
	clonedBlk.Header.Signature = nil

	publicKey, err := crypto.ParserPublicKeyWithSignatureAlgorithm(sig.PublicKey, int(sig.Algorithm))
	if err != nil {
		return false
	}
	ok, err := publicKey.Verify(clonedBlk.Marshal(), sig.Signature)
	if err != nil {
		return false
	}
	return ok
}

// UnmarshalBlock ...
func UnmarshalBlock(blockBytes []byte, blockPtr interface{}) error {
	blkPtr, ok := blockPtr.(*Block)
	if !ok {
		return errors.New("type of blockPtr must be *Block")
	}
	return proto.Unmarshal(blockBytes, blkPtr)
}

// UnmarshalBlockExecRst ...
func UnmarshalBlockExecRst(blockBytes []byte, blockPtr interface{}) error {
	blkPtr, ok := blockPtr.(*BlockExecResult)
	if !ok {
		return errors.New("type of blockPtr must be *BlockExecResult")
	}
	return proto.Unmarshal(blockBytes, blkPtr)
}

// SeqCenterBlockIndexIDGenerator 序列号中心的区块索引和文档ID生成函数,区块的docID一般是区块号，但序列中心是以区块序列号
func SeqCenterBlockIndexIDGenerator(doc *protos.EnhancedProtoMessage) (index string, docID string) {
	if doc == nil {
		return "", ""
	}
	if block, ok := (*doc).(*Block); ok {
		if block.Header != nil {
			return protos.EsIndexSeqCenterBlock, strconv.FormatUint(block.Header.BlockSequence, 10)
		}
	}
	return "", ""
}

// GetXXXNamespace ...
func (m *BlockExecResult) GetXXXNamespace() string {
	return protos.EsIndexBlockExecResult
}

// GetXXXKey ...
func (m *BlockExecResult) GetXXXKey() string {
	return strconv.FormatUint(m.BlockNumber, 10)
}

// GetXXXSearchFields ...
func (m *BlockExecResult) GetXXXSearchFields() map[string]interface{} {
	searchFields := make(map[string]interface{})
	searchFields["blockNumber"] = m.BlockNumber
	return searchFields
}

// GetXXXNamespace ...
func (m *ExecutedTransaction) GetXXXNamespace() string {
	return protos.EsIndexExecTx
}

// GetXXXKey ...
func (m *ExecutedTransaction) GetXXXKey() string {
	if m.Receipt == nil {
		return ""
	}
	return hex.EncodeToString(m.Receipt.TxHash)
}

// GetXXXSearchFields ...
func (m *ExecutedTransaction) GetXXXSearchFields() map[string]interface{} {
	if m.Receipt == nil {
		return nil
	}
	searchFields := make(map[string]interface{})
	searchFields["blockNumber"] = m.Receipt.BlockNumber
	searchFields["status"] = m.Receipt.TxStatus
	searchFields["indexOfBlock"] = m.Receipt.TransactionIndex
	searchFields["txHash"] = hex.EncodeToString(m.Receipt.TxHash)
	return searchFields
}

// GetXXXNamespace ...
func (m *PendingBlock) GetXXXNamespace() string {
	return protos.EsIndexPendingBlock
}

// GetXXXKey ...
func (m *PendingBlock) GetXXXKey() string {
	if m.Block.Header != nil {
		return strconv.FormatUint(m.Block.Header.BlockSequence, 10)
	}
	return ""
}

// GetXXXSearchFields ...
func (m *PendingBlock) GetXXXSearchFields() map[string]interface{} {
	if m.Block.Header == nil {
		return nil
	}
	searchFields := make(map[string]interface{})
	searchFields["number"] = m.Block.Header.Number
	return searchFields
}

// GetXXXNamespace ...
func (m *PendingTx) GetXXXNamespace() string {
	return protos.EsIndexPendingTx
}

// GetXXXKey ...
func (m *PendingTx) GetXXXKey() string {
	if m.Tx == nil {
		return ""
	}
	return hex.EncodeToString(m.Tx.Hash())
}

// GetXXXSearchFields ...
func (m *PendingTx) GetXXXSearchFields() map[string]interface{} {
	if m.Tx == nil {
		return nil
	}
	searchFields := make(map[string]interface{})
	searchFields["from"] = m.Tx.From
	searchFields["to"] = m.Tx.To
	if m.Tx.ContractId != nil {
		searchFields["contractId"] = fmt.Sprintf("%s/%s", m.Tx.ContractId.Name, m.Tx.ContractId.Version)
	}
	return searchFields
}

// GetXXXNamespace ...
func (m *BalanceFreezeUnfreezeRecord) GetXXXNamespace() string {
	return protos.EsIndexBalanceFreezeUnfreezeRecord
}

// GetXXXKey ...
func (m *BalanceFreezeUnfreezeRecord) GetXXXKey() string {
	return fmt.Sprintf("%d%s%d", m.FreezeTx.BlockNumber, protos.EsDocIDSeparator, m.FreezeTx.TransactionIndex)
}

// GetXXXSearchFields ...
func (m *BalanceFreezeUnfreezeRecord) GetXXXSearchFields() map[string]interface{} {
	return nil
}
